﻿using System;
using System.Linq;
using MediaBrowser.Library;
using MediaBrowser.Library.Entities;
using MediaBrowserWS.Service.Classes;
using MediaBrowserWS.Service.Enums;
using MediaBrowser.Library.RemoteControl;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Description;
using MediaBrowser.Library.Logging;
using System.Configuration;
using MediaBrowserWS.Code;
using System.IO;
using System.ServiceModel.Configuration;
using System.ServiceModel.Web;
using Microsoft.MediaCenter.Hosting;
using Microsoft.MediaCenter;
using System.Threading;
using Microsoft.Ajax.Samples;
using System.ServiceModel.Channels;
using System.Text;
using System.Net;
using System.Runtime.Serialization.Json;
using System.Xml.Linq;
using MediaBrowser.Library.Factories;

namespace MediaBrowserWS.Service
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    [ServiceKnownType(typeof(MetadataItem))]
    [ServiceKnownType(typeof(BasicCollectionItem))]
    public class MediaBrowserService : IMediaBrowserService, IWebPageService, IDirectImages
    {
        #region Private Members

        WebServiceHost _host;
        WebServicePlaybackController _controller;

        private static readonly string _imdbMobileTrailersVar = "var trailers = ";
        private static readonly string _localTrailerSource = "LOCAL";

        #endregion

        WebServicePlaybackController Controller
        {
            get
            {
                if (_controller == null)
                {
                    _controller = new WebServicePlaybackController();
                }

                return _controller;
            }   
        }

        #region IMediaBrowserService Members

        public PictureItem GetImage(Guid id, ImageTypes imageType, int maxHeight, int maxWidth)
        {
            PictureItem retVal = null;
            try
            {
                var item = Kernel.Instance.ItemRepository.RetrieveItem(id);
                retVal = new PictureItem(item, imageType, maxWidth, maxHeight);
                WebOperationContext.Current.OutgoingResponse.ContentType = GetContentType(retVal.FileExtension);
            }
            catch (Exception ex)
            {
                LogMessage("Error Retrieving Image for id:'{0}'  :'{1}' ERROR:'{2}'", id, imageType, ex.Message);
            }

            return retVal;
        }

        public MetadataItem GetMetadata(Guid id)
        {
            MetadataItem retVal = null;
            try
            {
                var bob = Kernel.Instance.ItemRepository.RetrieveItem(id);
                retVal = new MetadataItem(bob);
            }
            catch (Exception ex)
            {
                LogMessage("Error getting item '{0}'; ERROR:'{1}'", id, ex.Message);
            }

            return retVal;
        }

        public bool PlayItem(Guid id)
        {
            LogMessage("Attempting to play Id '{0}'", id.ToString());
            bool retVal = false;
            try
            {
                var item = Kernel.Instance.ItemRepository.RetrieveItem(id);

                // get the file attributes for file or directory
                FileAttributes attr = File.GetAttributes(item.Path);

                MediaCollection collection;
                // detect whether its a directory or file
                if ((attr & FileAttributes.Directory) == FileAttributes.Directory)
                    collection = GetCollectionFromDirectory(item.Path);
                else
                    collection = GetCollectionFromFile(item.Path);

                bool isFirst = true;
                foreach (var collectionItem in collection)
                {
                    if (isFirst)
                    {
                        retVal = AddInHost.Current.MediaCenterEnvironment.PlayMedia(Microsoft.MediaCenter.MediaType.Video,
                            collectionItem.Media.ToString(),
                            false);
                        isFirst = false;
                    }
                    else
                    {
                        AddInHost.Current.MediaCenterEnvironment.PlayMedia(Microsoft.MediaCenter.MediaType.Video,
                            collectionItem.Media.ToString(),
                            true);
                    }
                }


                if (retVal && !AddInHost.Current.MediaCenterEnvironment.MediaExperience.IsFullScreen)
                {
                    Thread.Sleep(200);
                    AddInHost.Current.MediaCenterEnvironment.MediaExperience.GoToFullScreen();
                }
            }
            catch (Exception ex)
            {
                LogMessage("Error playing file {0}; ERROR: {1}", id, ex.Message);
            }

            return retVal;
        }

        public NowPlayingItem GetNowPlaying(bool waitForUpdate)
        {
            NowPlayingItem item = Controller.GetNowPlaying(waitForUpdate);
            return item;
        }

        public BasicItem GetItem(Guid id)
        {
            BasicItem retVal = null;
            try
            {
                var bob = Kernel.Instance.ItemRepository.RetrieveItem(id);
                if (bob is Folder)
                {
                    // collection
                    retVal = new BasicCollectionItem(bob);
                }
                else
                {
                    retVal = new MetadataItem(bob);
                }
            }
            catch (Exception ex)
            {
                LogMessage("Error getting item '{0}'; ERROR:'{1}'", id, ex.Message);
            }

            return retVal;
        }

        public BasicCollectionItem GetStartUpFolders()
        {
            BasicCollectionItem retVal = null;
            try
            {
                AggregateFolder aFolder = Kernel.Instance.RootFolder;
                retVal = new BasicCollectionItem(aFolder);
            }
            catch (Exception ex)
            {
                LogMessage("Error getting start up folders ERROR:'{0}'", ex.Message);
            }

            return retVal;
        }

        public BasicCollectionItem GetCollection(Guid id)
        {
            BasicCollectionItem retVal = null;

            try
            {
                BaseItem item = Kernel.Instance.ItemRepository.RetrieveItem(id);
                if (item != null)
                {
                    BasicCollectionItem collection = new BasicCollectionItem(item);
                    retVal = collection;
                }
            }
            catch (Exception ex)
            {
                LogMessage("Error getting collection '{0}'; ERROR:'{1}'", id, ex.Message);
            }

            return retVal;
        }

        public void SendCommand(CommandTypes command)
        {
            try
            {
                LogMessage("Received command '{0}'", command.ToString());
                switch (command)
                {
                    case CommandTypes.VolDown:
                        {
                            AddInHost.Current.MediaCenterEnvironment.AudioMixer.VolumeDown();
                            break;
                        }
                    case CommandTypes.VolUp:
                        {
                            AddInHost.Current.MediaCenterEnvironment.AudioMixer.VolumeUp();
                            break;
                        }
                    default:
                        {
                            SendKeyCommander.SendCommand(command);
                            break;
                        }
                }
            }
            catch (Exception ex)
            {
                Logging.Logger.Instance.LogMessage("Error sending command {0}; ERROR: {1}", command, ex.Message);
            }
        }

        public void Seek(int seconds)
        {
            Logging.Logger.Instance.LogMessage("Seeking to {0}", seconds);
            try
            {
                TimeSpan time = TimeSpan.FromSeconds(seconds);
                Controller.Seek(time.Ticks);
            }
            catch (Exception ex)
            {
                Logging.Logger.Instance.LogMessage("Error seeking to time {0}; ERROR: {1}", seconds, ex.Message);
            }
        }

        public void GoToScreen(ScreenType screenType, string extraInfo)
        {
            try
            {
                AddInHost.Current.MediaCenterEnvironment.NavigateToPage((PageId)((int)screenType), extraInfo);
            }
            catch (Exception ex)
            {
                Logging.Logger.Instance.LogMessage("Error navigating to screen{0}; ERROR: {1}", screenType, ex.Message);
            }
        }

        public TrailerInfo[] GetTrailers(Guid id)
        {
            List<TrailerInfo> trailers = new List<TrailerInfo>();

            // Check the cache
            FileInfo[] files = AppSettings.TrailersCacheDirectory.GetFiles(string.Format("{0}*", id));
            if (files.Length == 1)
            {
                // try parsing the cache
                trailers.AddRange(ParseTrailerCacheFile(files[0]));
            }

            if (trailers.Count == 0)
            {
                // nothing was found in the cache, so try from scratch
                var mbItem = Kernel.Instance.ItemRepository.RetrieveItem(id);

                if (mbItem != null)
                {
                    var item = new MetadataItem(mbItem);
                    if (!item.ImdbId.IsNullOrEmpty())
                    {
                        trailers.AddRange(ScrapeImdbMobile(item.ImdbId));
                    }

                    trailers.AddRange(GetLocalTrailers(mbItem));
                }

                CacheTrailers(id, trailers);
            }

            return trailers.ToArray();
        }

        private IEnumerable<TrailerInfo> ParseTrailerCacheFile(FileInfo fileInfo)
        {
            List<TrailerInfo> trailers = new List<TrailerInfo>();
            try
            {
                XDocument doc = XDocument.Load(fileInfo.FullName);
                var trailerParts = from bob in doc.Descendants("TrailerInfo")
                               select new TrailerInfo
                               {
                                   format = bob.Element("format").Value,
                                   resolution = bob.Element("resolution").Value,
                                   source = bob.Element("source").Value,
                                   url = bob.Element("url").Value,
                               };
                trailers.AddRange(trailerParts);
            }
            catch (Exception ex)
            {
                Logging.Logger.Instance.LogMessage("Error parsing trailer file {0}; ERROR: {1}", fileInfo.FullName, ex.Message);
            }

            return trailers;
        }

        private void CacheTrailers(Guid id, List<TrailerInfo> trailers)
        {
            XElement trailerElems = new XElement("Trailers",
                                from trailer in trailers
                                select new XElement("TrailerInfo",
                                    new XElement("format", trailer.format),
                                    new XElement("resolution", trailer.resolution),
                                    new XElement("source", trailer.source),
                                    new XElement("url", trailer.url)));

            XDocument doc = new XDocument(trailerElems);
            string filePath = Path.Combine(AppSettings.TrailersCacheDirectory.FullName, string.Format("{0}.xml", id));
            doc.Save(filePath);
        }

        public void PlayTrailer(TrailerInfo trailer)
        {
            try
            {
                if (trailer != null)
                {
                    if (trailer.source == _localTrailerSource && !trailer.url.IsNullOrEmpty() && trailer.url.Contains("_"))
                    {
                        // play local trailer
                        string[] urls = trailer.url.Split(new string[] {"_"}, StringSplitOptions.RemoveEmptyEntries);
                        if (urls.Length == 2)
                        {
                            Guid id = new Guid(urls[0]);
                            var item = Kernel.Instance.ItemRepository.RetrieveItem(id);
                            if (item != null && item is Movie)
                            {
                                Movie movie = item as Movie;
                                string trailerPath = movie.TrailerFiles.FirstOrDefault(o => Path.GetFileNameWithoutExtension(o) == urls[1]);
                                if (!trailerPath.IsNullOrEmpty())
                                {
                                    // play streamed trailer
                                    AddInHost.Current.MediaCenterEnvironment.PlayMedia(Microsoft.MediaCenter.MediaType.Video,
                                           trailerPath,
                                           false);
                                }
                            }
                        }
                    }
                    else if (!trailer.url.IsNullOrEmpty())
                    {
                        // play streamed trailer
                        AddInHost.Current.MediaCenterEnvironment.PlayMedia(Microsoft.MediaCenter.MediaType.Video,
                               trailer.url,
                               false);
                    }
                }
            }
            catch (Exception ex)
            {
                Logging.Logger.Instance.LogMessage("Error trying to play trailer: {0},{1}; ERROR: {2}", trailer.source, trailer.url, ex.Message);
            }
        }

        #endregion

        #region IWebPageService Members

        public Stream GetWebFile(string filename)
        {
            Stream retVal = null;

            if (filename == "test")
            {
                retVal = Test();
            }
            else if (filename.EndsWith(".html"))
            {
                retVal = GetWebFileInDir(string.Empty, filename);
            }

            return retVal;
        }


        public Stream GetWebFileInDir(string filetype, string filename)
        {
            Stream retVal = null;

            try
            {
                string extension = Path.GetExtension(filename).Replace(".", string.Empty);

                if (!filename.IsNullOrEmpty())
                {
                    if (AppSettings.MimeTypes.ContainsKey(extension))
                    {
                        if (!filetype.IsNullOrEmpty())
                            filetype += "\\"; // add the path separated.
                        
                        FileInfo file = new FileInfo(Path.Combine(AppSettings.WebPageBaseDirectory.FullName, string.Format(@"{0}{1}", filetype, filename)));
                        if (File.Exists(file.FullName))
                        {
                            retVal = new FileStream(file.FullName, FileMode.Open, FileAccess.Read);
                            
                        }
                        else if (extension == "html")
                        {
                            // return a '404' page
                            retVal = GetWebPageAsStream("Invalid file path", 
                                string.Format("ERROR <br /><br />Sorry, could not find the file you requested, ensure the file '{0}' exists", file.FullName));
                        }
                    }

                    WebOperationContext.Current.OutgoingResponse.ContentType = GetContentType(extension);
                }
            }
            catch (Exception ex)
            {
                Logging.Logger.Instance.LogMessage("Error getting web file {0},{1}; ERROR: {2}", filetype, filename, ex.Message);
            }
            return retVal;
        }

        public Stream Test()
        {
            Stream stream = GetWebPageAsStream("Test Page", "Service is running successfully.<br /> <br />Version: 0.9 beta2 (Though this is potentiallyy not very reliable...)");
            WebOperationContext.Current.OutgoingResponse.ContentType = GetContentType("html");
            return stream;
        }

        #endregion

        #region IDirectImages

        public Stream GetDirectImage(Guid id, ImageTypes imageType, int maxHeight, int maxWidth)
        {
            PictureItem item = GetImage(id, imageType, maxHeight, maxWidth);
            WebOperationContext.Current.OutgoingResponse.ContentType = GetContentType(item.FileExtension);
            return item.PictureStream;
        }

        #endregion

        public void Initialise(Uri uri)
        {
            _host = new WebServiceHost(this, uri);
            //WebHttpBinding binding = new WebHttpBinding
            //{
            //    CrossDomainScriptAccessEnabled = true
            //};

            JSONPBindingElement bindingElement = new JSONPBindingElement();
            CustomBinding binding = new CustomBinding(bindingElement, new HttpTransportBindingElement
            {
                ManualAddressing  = true
            });
            _host.AddServiceEndpoint(typeof(IMediaBrowserService), binding, string.Empty)
                .Behaviors.Add(new WebHttpBehavior());

            _host.AddServiceEndpoint(typeof(IWebPageService), new WebHttpBinding(), "web")
                .Behaviors.Add(new WebHttpBehavior());
            _host.AddServiceEndpoint(typeof(IDirectImages), new WebHttpBinding(), "directimage")
                .Behaviors.Add(new WebHttpBehavior());
            _host.Open();

            _controller = new WebServicePlaybackController();
        }

        private MediaCollection GetCollectionFromFile(string path)
        {
            MediaCollection collection = new MediaCollection();
            collection.Add(new MediaCollectionItem
            {
                Media = path
            });

            return collection;
        }

        private MediaCollection GetCollectionFromDirectory(string p)
        {
            MediaCollection collection = new MediaCollection();
            DirectoryInfo dir = new DirectoryInfo(p);
            FileInfo[] files = dir.GetFiles();
            var videos = files.Where(o => MediaBrowser.LibraryManagement.Helper.IsVideo(o.FullName));

            foreach (var vid in videos)
            {
                collection.Add(new MediaCollectionItem
                {
                    Media = vid.FullName
                });
            }

            return collection;
        }

        private void LogMessage(string message, params object[] stringFormatArgs)
        {
            MediaBrowserWS.Logging.Logger.Instance.LogMessage(message, stringFormatArgs);
        }

        private string GetContentType(string type)
        {
            string retVal = string.Empty;

            if (AppSettings.MimeTypes.ContainsKey(type))
            {
                retVal = AppSettings.MimeTypes[type];
            }

            return retVal;
        }

        private Stream GetWebPageAsStream(string title, string content)
        {
            string html = string.Format(@"<html><head><title>{0}</title></head><body>{1}</body></html>", title, content);
            var bytes = Encoding.ASCII.GetBytes(html);
            MemoryStream stream = new MemoryStream(bytes);
            return stream;   
        }
        
        private IEnumerable<TrailerInfo> ScrapeImdbMobile(string imdbId)
        {
            List<TrailerInfo> trailers = new List<TrailerInfo>();

            string url = string.Format("http://m.imdb.com/title/{0}", imdbId);
            string contents = string.Empty;

            try
            {
                WebResponse objResponse;
                WebRequest objRequest = HttpWebRequest.Create(url);

                objResponse = objRequest.GetResponse();
                if (objResponse != null)
                {
                    using (StreamReader sr = new StreamReader(objResponse.GetResponseStream()))
                    {
                        contents = sr.ReadToEnd();
                        // Close and clean up the StreamReader
                        sr.Close();
                    }

                    // scrape contents for trailers...
                    int index = contents.IndexOf(_imdbMobileTrailersVar);
                    if (index > 0)
                    {
                        string part = contents.Substring(index + _imdbMobileTrailersVar.Length);
                        int endIndex = part.IndexOf(';');
                        string toParse = part.Substring(0, endIndex);

                        DataContractJsonSerializer serialiser = new DataContractJsonSerializer(typeof(TrailerInfo[]));
                        MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(toParse));
                        object items = serialiser.ReadObject(stream);
                        if (items is TrailerInfo[])
                        {
                            TrailerInfo[] TrailerInfos = items as TrailerInfo[];
                            foreach (var bob in TrailerInfos)
                            {
                                string[] formSplit = bob.format.Split(' ');
                                string format = formSplit[0];
                                string resolution = formSplit[1];
                                // dummy it..
                                TrailerInfo info = new TrailerInfo
                                {
                                    format = format,
                                    resolution = resolution,
                                    url = bob.url,
                                    source = "IMDB-Mobile"
                                };
                                trailers.Add(info);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Logging.Logger.Instance.LogMessage("Error scraping imdb for Id: {0}; ERROR: {1}", imdbId, ex.Message);
            }

            return trailers;
        }
     
        private IEnumerable<TrailerInfo> GetLocalTrailers(BaseItem mbItem)
        {
            if (mbItem is Movie)
            {
                Movie movie = mbItem as Movie;
                if (movie.ContainsTrailers)
                {
                    foreach (var trailer in movie.TrailerFiles)
                    {
                        yield return new TrailerInfo
                        {
                            source = _localTrailerSource,
                            url = string.Format("{0}_{1}", movie.Id, Path.GetFileNameWithoutExtension(trailer))
                        };
                    }
                }
            }
        }
    }
}
